﻿using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

using AstoriaPlus.Extensions;

namespace AstoriaPlus
{
	public static class DataServiceRulesExtensions
	{
		private static Dictionary<Type, object> extendedConfigurations = new Dictionary<Type, object>();

		public static IDataServiceRules<TDataService, TDataSource> ForService<TDataService, TDataSource>(this IDataServiceConfiguration configuration)
			where TDataService : DataService<TDataSource>
			where TDataSource : class
		{
			var key = typeof(TDataService);
			if (key.IsAbstract)
			{
				throw new ArgumentException("TDataService cannot be an abstract class type. You must use the concrete class type of your data service.");
			}
			// TODO: check that key is not an abstract type, nor is directly DataService<T>
			object extendedConfig;
			if (!extendedConfigurations.TryGetValue(key, out extendedConfig))
			{
				extendedConfig = new ExtendedDataServiceConfiguration<TDataService, TDataSource>(configuration);
				//TODO : lock or no need? InitializeService should be called only once.
				extendedConfigurations[key] = extendedConfig;
			}

			return new DataServiceRules<TDataService, TDataSource>((ExtendedDataServiceConfiguration<TDataService, TDataSource>)extendedConfig);
		}

		public static void ExecuteChangeInterceptors<TDataService, TDataSource>(this TDataService service, TDataSource dataSource, object entity, UpdateOperations operations)
			where TDataService : DataService<TDataSource>
			where TDataSource : class
		{
			var configuration = GetConfiguration<TDataService, TDataSource>();

			foreach (var interceptorFactory in configuration.ChangeInterceptors)
			{
				var interceptor = interceptorFactory.Create();
				interceptor.Service = service;
				interceptor.DataSource = dataSource;
				interceptor.Entity = entity;
				interceptor.UpdateOperations = operations;

				interceptor.Execute();
			}
		}

		//public static Expression<Func<TEntity, bool>> QueryInterceptorFor<TDataService, TEntity>(this TDataService service, Expression<Func<IQueryable<TEntity>>> entitySetProperty)
		//    where TEntity : class
		//{
		//    // TODO: add caching to avoid unnecessary reflection
		//    Type dataSourceType = GetDataSourceType(entitySetProperty);
		//    var configuration = GetConfiguration<TDataService>();
		//    Type queryInterceptorsType = typeof(QueryInterceptors<,>).MakeGenericType(typeof(TDataService), dataSourceType);
		//    var queryInterceptors = Activator.CreateInstance(queryInterceptorsType, configuration) as IQueryInterceptors;
		//    return queryInterceptors.For<TEntity>();
		//}

		//private static Type GetDataSourceType(LambdaExpression nestedPropertyExpression)
		//{
		//    var property = nestedPropertyExpression.Body as MemberExpression;
		//    if (property == null || !(property.Member is PropertyInfo))
		//    {
		//        throw new ArgumentException(string.Format(
		//            CultureInfo.CurrentCulture,
		//            "Expression must be of the form '() => x.CurrentDataSource.EntitySetName'. Invalid expression '{0}'.",
		//            nestedPropertyExpression), "nestedPropertyExpression");
		//    }

		//    return property.Member.DeclaringType;
		//}

		public static Expression<Func<TEntity, bool>> QueryInterceptorFor<TDataService, TDataSource, TEntity>(this TDataService service, TDataSource dataSource, Expression<Func<TDataSource, IQueryable<TEntity>>> entitySetProperty)
			where TDataService : DataService<TDataSource>
			where TDataSource : class
			where TEntity : class
		{
			var configuration = GetConfiguration<TDataService, TDataSource>();
			var queryInterceptors = new QueryInterceptors<TDataService, TDataSource>(configuration);
			return queryInterceptors.For<TEntity>(new DataServiceQueryContext<TDataService, TDataSource>(service, dataSource));
		}

		private static ExtendedDataServiceConfiguration<TDataService, TDataSource> GetConfiguration<TDataService, TDataSource>()
			where TDataService : DataService<TDataSource>
			where TDataSource : class
		{
			var configuration = GetConfiguration<TDataService>() as ExtendedDataServiceConfiguration<TDataService, TDataSource>;
			if (configuration == null)
			{
				throw new InvalidOperationException("You must first define a public static InitializeService method in your service Type, and call config.ForService<TDataService, TDataSource>, where TDataService is the concrete type of your data service.");
			}

			return configuration;
		}

		private static object GetConfiguration<TDataService>()
		{
			var key = typeof(TDataService);
			var configuration = extendedConfigurations[key];
			return configuration;
		}
	}
}
